package edu.northwestern.cbits.purple_robot_manager.probes.builtin;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import edu.northwestern.cbits.purple_robot_manager.EncryptionManager;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference;
import edu.northwestern.cbits.purple_robot_manager.db.DistancesProvider;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
public class AddressBookDistancesProbe extends Probe
{
private static final boolean DEFAULT_ENABLED = false;
private static final String DEFAULT_FREQUENCY = "3600000";
private static final String NOW = "NOW";
private static final String FREQUENCY = "config_probe_distances_frequency";
private static final String ENABLED = "config_probe_distances_enabled";
private static final String HASH_DATA = "config_probe_distances_hash_data";
private long _lastCheck = 0;
private double _lastLatitude = 100;
private double _lastLongitude = 200;
private LocationListener _listener = null;
@Override
public String getPreferenceKey() {
return "built_in_distances";
}
@Override
public String name(Context context)
{
return "edu.northwestern.cbits.purple_robot_manager.probes.builtin.AddressBookDistancesProbe";
}
@Override
public String title(Context context)
{
return context.getString(R.string.title_distances_probe);
}
@Override
public String probeCategory(Context context)
{
return context.getResources().getString(R.string.probe_personal_info_category);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(AddressBookDistancesProbe.ENABLED, true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(AddressBookDistancesProbe.ENABLED, false);
e.commit();
}
@Override
public boolean isEnabled(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
if (super.isEnabled(context))
{
long now = System.currentTimeMillis();
if (prefs.getBoolean(AddressBookDistancesProbe.ENABLED, AddressBookDistancesProbe.DEFAULT_ENABLED))
{
HashMap<String, String> addresses = new HashMap<>();
long freq = Long.parseLong(prefs.getString(AddressBookDistancesProbe.FREQUENCY, AddressBookDistancesProbe.DEFAULT_FREQUENCY));
boolean doHash = prefs.getBoolean(AddressBookDistancesProbe.HASH_DATA, Probe.DEFAULT_HASH_DATA);
synchronized (this)
{
final AddressBookDistancesProbe me = this;
if (this._listener == null)
{
this._listener = new LocationListener()
{
@Override
public void onLocationChanged(Location location)
{
me._lastLatitude = location.getLatitude();
me._lastLongitude = location.getLongitude();
}
@Override
public void onProviderDisabled(String provider)
{
}
@Override
public void onProviderEnabled(String provider)
{
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
}
};
if (Looper.myLooper() == null)
Looper.prepare();
locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this._listener);
}
else if (now - this._lastCheck > freq && this._lastLatitude < 90 && this._lastLongitude < 180)
{
Bundle bundle = new Bundle();
bundle.putString("PROBE", this.name(context));
bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000);
this._lastCheck = now;
String selection = ContactsContract.Groups.TITLE + " LIKE ?";
String[] args =
{ "Purple Robot%" };
Cursor cursor = context.getContentResolver().query(ContactsContract.Groups.CONTENT_URI, null, selection, args, null);
while (cursor.moveToNext())
{
String membersSelection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ?";
String[] membersArgs =
{ cursor.getString(cursor.getColumnIndex(ContactsContract.Groups._ID)) };
String[] membersProjection =
{ ContactsContract.CommonDataKinds.GroupMembership.CONTACT_ID, ContactsContract.Contacts.DISPLAY_NAME };
Cursor membersCursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, membersProjection, membersSelection, membersArgs, null);
while (membersCursor.moveToNext())
{
int contactId = membersCursor.getInt(membersCursor.getColumnIndex(ContactsContract.CommonDataKinds.GroupMembership.CONTACT_ID));
String contactName = membersCursor.getString(membersCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
String addressSelection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
String[] addressArgs =
{ "" + contactId, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE };
String[] projection = new String[]
{ StructuredPostal.FORMATTED_ADDRESS, StructuredPostal.TYPE }; // ,
Cursor addressCursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, projection, addressSelection, addressArgs, null);
while (addressCursor.moveToNext())
{
String address = addressCursor.getString(addressCursor.getColumnIndex(StructuredPostal.FORMATTED_ADDRESS));
int type = addressCursor.getInt(addressCursor.getColumnIndex(StructuredPostal.TYPE));
String label = context.getString(R.string.config_probe_distances_label_other);
if (type == StructuredPostal.TYPE_HOME)
label = context.getString(R.string.config_probe_distances_label_home);
else if (type == StructuredPostal.TYPE_WORK)
label = context.getString(R.string.config_probe_distances_label_work);
label = contactName + ": " + label;
if (doHash)
label = EncryptionManager.getInstance().createHash(context, label);
addresses.put(label, address);
}
addressCursor.close();
}
membersCursor.close();
}
cursor.close();
Location here = new Location("Purple Robot");
here.setLatitude(this._lastLatitude);
here.setLongitude(this._lastLongitude);
Bundle nowDistances = this.distancesForDays(context, here, addresses, 0, now, false);
if (nowDistances != null)
bundle.putBundle(AddressBookDistancesProbe.NOW, nowDistances);
Bundle todayDistances = this.distancesForDays(context, here, addresses, 1, now, false);
if (todayDistances != null)
bundle.putBundle("TODAY_AVERAGE", todayDistances);
Bundle weekDistances = this.distancesForDays(context, here, addresses, 7, now, false);
if (weekDistances != null)
bundle.putBundle("WEEK_AVERAGE", weekDistances);
Bundle monthDistances = this.distancesForDays(context, here, addresses, 28, now, true);
if (monthDistances != null)
bundle.putBundle("MONTH_AVERAGE", monthDistances);
this.transmitData(context, bundle);
}
}
return true;
}
}
if (this._listener != null)
{
locationManager.removeUpdates(this._listener);
this._listener = null;
}
return false;
}
@SuppressLint("DefaultLocale")
private Bundle distancesForDays(Context context, Location here, HashMap<String, String> addresses, long days, long now, boolean clear)
{
long start = now - (days * 24 * 60 * 60 * 1000);
SharedPreferences prefs = Probe.getPreferences(context);
Bundle bundle = new Bundle();
if (days == 0)
{
for (String label : addresses.keySet())
{
String address = addresses.get(label).trim().toLowerCase().replace("\n", " ").replace("\r", " ");
while (address.contains(" "))
address = address.replace(" ", " ");
String key = "Geocoded Location: " + address;
String locationJson = prefs.getString(key, null);
Location there = null;
try
{
if (locationJson == null)
{
Geocoder geo = new Geocoder(context);
List<Address> matches = geo.getFromLocationName(address, 1);
if (matches.size() > 0)
{
Address match = matches.get(0);
JSONObject json = new JSONObject();
json.put("latitude", match.getLatitude());
json.put("longitude", match.getLongitude());
locationJson = json.toString();
Editor e = prefs.edit();
e.putString(key, locationJson);
e.commit();
there = new Location("Purple Robot");
there.setLatitude(match.getLatitude());
there.setLongitude(match.getLongitude());
}
else
{
throw new Exception("Unable to find location for '" + address + "'.");
}
}
else
{
JSONObject json = new JSONObject(locationJson);
there = new Location("Purple Robot");
there.setLatitude(json.getDouble("latitude"));
there.setLongitude(json.getDouble("longitude"));
}
}
catch (Throwable e)
{
LogManager.getInstance(context).logException(e);
}
if (there != null)
{
float distance = here.distanceTo(there);
bundle.putFloat(label, distance);
ContentValues value = new ContentValues();
value.put(DistancesProvider.NAME, label);
value.put(DistancesProvider.DISTANCE, distance);
value.put(DistancesProvider.TIMESTAMP, now);
context.getContentResolver().insert(DistancesProvider.CONTENT_URI, value);
}
}
}
else
{
for (String label : addresses.keySet())
{
String testSelection = DistancesProvider.NAME + " = ? AND " + DistancesProvider.TIMESTAMP + " < ?";
String[] testSelectionArgs =
{ label, "" + start };
String[] testProjection =
{ DistancesProvider.DISTANCE, DistancesProvider.TIMESTAMP };
Cursor testCursor = context.getContentResolver().query(DistancesProvider.CONTENT_URI, testProjection, testSelection, testSelectionArgs, null);
boolean goOn = (testCursor.getCount() > 0);
testCursor.close();
if (goOn)
{
String selection = DistancesProvider.NAME + " = ? AND " + DistancesProvider.TIMESTAMP + " >= ? AND " + DistancesProvider.TIMESTAMP + " <= ?";
String[] selectionArgs =
{ label, "" + start, "" + now };
String[] projection =
{ DistancesProvider.DISTANCE, DistancesProvider.TIMESTAMP };
Cursor cursor = context.getContentResolver().query(DistancesProvider.CONTENT_URI, projection, selection, selectionArgs, null);
DescriptiveStatistics stats = new DescriptiveStatistics();
while (cursor.moveToNext())
{
double distance = cursor.getDouble(cursor.getColumnIndex(DistancesProvider.DISTANCE));
stats.addValue(distance);
}
cursor.close();
if (stats.getN() > 0)
{
Bundle placeStats = new Bundle();
placeStats.putDouble("MEAN", stats.getMean());
placeStats.putDouble("MIN", stats.getMin());
placeStats.putDouble("MAX", stats.getMax());
placeStats.putDouble("STD_DEV", stats.getStandardDeviation());
placeStats.putDouble("COUNT", stats.getN());
bundle.putBundle(label, placeStats);
}
}
}
if (clear)
{
String deleteSelection = DistancesProvider.TIMESTAMP + " < ?";
String[] deleteArgs =
{ "" + start };
context.getContentResolver().delete(DistancesProvider.CONTENT_URI, deleteSelection, deleteArgs);
}
}
if (bundle.keySet().size() == 0)
return null;
return bundle;
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
float minDistance = Float.MAX_VALUE;
String minLabel = "";
Bundle now = bundle.getBundle(AddressBookDistancesProbe.NOW);
for (String key : now.keySet())
{
float distance = now.getFloat(key);
if (distance < minDistance)
{
minDistance = distance;
minLabel = key;
}
}
return context.getResources().getString(R.string.summary_distances_probe, minLabel, minDistance);
}
@Override
public Map<String, Object> configuration(Context context)
{
Map<String, Object> map = super.configuration(context);
SharedPreferences prefs = Probe.getPreferences(context);
long freq = Long.parseLong(prefs.getString(AddressBookDistancesProbe.FREQUENCY, AddressBookDistancesProbe.DEFAULT_FREQUENCY));
map.put(Probe.PROBE_FREQUENCY, freq);
boolean hash = prefs.getBoolean(AddressBookDistancesProbe.HASH_DATA, Probe.DEFAULT_HASH_DATA);
map.put(Probe.HASH_DATA, hash);
return map;
}
@Override
public void updateFromMap(Context context, Map<String, Object> params)
{
super.updateFromMap(context, params);
if (params.containsKey(Probe.PROBE_FREQUENCY))
{
Object frequency = params.get(Probe.PROBE_FREQUENCY);
if (frequency instanceof Double)
{
frequency = ((Double) frequency).longValue();
}
if (frequency instanceof Long)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putString(AddressBookDistancesProbe.FREQUENCY, frequency.toString());
e.commit();
}
}
if (params.containsKey(Probe.HASH_DATA))
{
Object hash = params.get(Probe.HASH_DATA);
if (hash instanceof Boolean)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(AddressBookDistancesProbe.HASH_DATA, (Boolean) hash);
e.commit();
}
}
}
@Override
@SuppressWarnings("deprecation")
public PreferenceScreen preferenceScreen(Context context, PreferenceManager manager)
{
PreferenceScreen screen = super.preferenceScreen(context, manager);
screen.setTitle(this.title(context));
screen.setSummary(R.string.summary_distances_probe_desc);
CheckBoxPreference enabled = new CheckBoxPreference(context);
enabled.setTitle(R.string.title_enable_probe);
enabled.setKey(AddressBookDistancesProbe.ENABLED);
enabled.setDefaultValue(AddressBookDistancesProbe.DEFAULT_ENABLED);
screen.addPreference(enabled);
FlexibleListPreference duration = new FlexibleListPreference(context);
duration.setKey(AddressBookDistancesProbe.FREQUENCY);
duration.setEntryValues(R.array.probe_distance_frequency_values);
duration.setEntries(R.array.probe_distance_frequency_labels);
duration.setTitle(R.string.probe_frequency_label);
duration.setDefaultValue(AddressBookDistancesProbe.DEFAULT_FREQUENCY);
screen.addPreference(duration);
CheckBoxPreference hash = new CheckBoxPreference(context);
hash.setKey(AddressBookDistancesProbe.HASH_DATA);
hash.setDefaultValue(Probe.DEFAULT_HASH_DATA);
hash.setTitle(R.string.config_probe_distances_hash_title);
hash.setSummary(R.string.config_probe_distances_hash_summary);
screen.addPreference(hash);
return screen;
}
@Override
public JSONObject fetchSettings(Context context)
{
JSONObject settings = super.fetchSettings(context);
try
{
JSONArray values = new JSONArray();
values.put(true);
values.put(false);
JSONObject hash = new JSONObject();
hash.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_BOOLEAN);
hash.put(Probe.PROBE_VALUES, values);
settings.put(Probe.HASH_DATA, hash);
JSONObject frequency = new JSONObject();
frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
values = new JSONArray();
String[] options = context.getResources().getStringArray(R.array.probe_distance_frequency_values);
for (String option : options)
{
values.put(Long.parseLong(option));
}
frequency.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_FREQUENCY, frequency);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
return settings;
}
@Override
public String summary(Context context)
{
return context.getString(R.string.summary_distances_probe_desc);
}
/*
* @SuppressWarnings("unchecked") public Bundle formattedBundle(Context
* context, Bundle bundle) { Bundle formatted =
* super.formattedBundle(context, bundle);
*
* ArrayList<Bundle> array = (ArrayList<Bundle>)
* bundle.get(AddressBookDistancesProbe.PHONE_CALLS);
*
* int count = array.size();
*
* Bundle callsBundle = this.bundleForCallArray(context, array);
*
* formatted.putBundle(String.format(context.getString(R.string.
* display_calls_list_title), count), callsBundle);
*
* formatted.putString(context.getString(R.string.
* display_calls_recent_caller_title),
* bundle.getString(AddressBookDistancesProbe.RECENT_CALLER));
* formatted.putString
* (context.getString(R.string.display_calls_recent_number_title),
* bundle.getString(AddressBookDistancesProbe.RECENT_NUMBER));
*
* Date d = new Date(bundle.getLong(AddressBookDistancesProbe.RECENT_TIME));
*
* formatted.putString(context.getString(R.string.
* display_calls_recent_time_title), d.toString());
*
* formatted.putInt(context.getString(R.string.
* display_calls_incoming_count_title), (int)
* bundle.getDouble(AddressBookDistancesProbe.CALL_INCOMING_COUNT));
* formatted
* .putInt(context.getString(R.string.display_calls_missed_count_title),
* (int) bundle.getDouble(AddressBookDistancesProbe.CALL_MISSED_COUNT));
* formatted
* .putInt(context.getString(R.string.display_calls_outgoing_count_title),
* (int) bundle.getDouble(AddressBookDistancesProbe.CALL_OUTGOING_COUNT));
* formatted
* .putInt(context.getString(R.string.display_sms_incoming_count_title),
* (int) bundle.getDouble(AddressBookDistancesProbe.SMS_INCOMING_COUNT));
* formatted
* .putInt(context.getString(R.string.display_sms_outgoing_count_title),
* (int) bundle.getDouble(AddressBookDistancesProbe.SMS_OUTGOING_COUNT));
*
*
* ArrayList<String> keys = new ArrayList<String>();
* keys.add(String.format(context
* .getString(R.string.display_calls_list_title), count));
* keys.add(context.getString(R.string.display_calls_recent_caller_title));
* keys.add(context.getString(R.string.display_calls_recent_number_title));
* keys.add(context.getString(R.string.display_calls_recent_time_title));
* keys.add(context.getString(R.string.display_calls_incoming_count_title));
* keys.add(context.getString(R.string.display_calls_missed_count_title));
* keys.add(context.getString(R.string.display_calls_outgoing_count_title));
* keys.add(context.getString(R.string.display_sms_incoming_count_title));
* keys.add(context.getString(R.string.display_sms_outgoing_count_title));
*
* formatted.putStringArrayList("KEY_ORDER", keys);
*
* return formatted; }
*/
}